<?php
/**
 * 
 * Due to PHP issues, usage of this script is NOT possible with Safe Mode enabled.
 *  
 */

define('CHUNKSIZE',		'1024768');			// Default pass-through chunk size (used when extracting uncompressed data): 1Mb
define('MAXBATCHSIZE',	1024768);			// Maximum archive chunk's size to be processed at once: 2Mb
define('MAXBATCHFILES', 40);					// Maximum files to process at once: 40
define('DRYRUN', 0);						// Set to 1 for Test mode (no extraction, no deletion)

// ==========================================================================================
// KS: Simple Unzip Class
// ==========================================================================================

class CSimpleUnzip
{
	/**
	 * Absolute file name and path of the ZIP file
	 *
	 * @var string
	 */
	var $_filename = '';
	
	/**
	 * Read only file pointer to the ZIP file
	 *
	 * @var unknown_type
	 */
	var $_filepointer = false;
	
	/**
	 * Did we encounter an error?
	 *
	 * @var boolean
	 */
	var $_isError = false;
	
	/**
	 * Error description
	 *
	 * @var string
	 */
	var $_error = '';
	
	/**
	 * Creates the CSimpleUnzip object and tries to open the file specified in $filename
	 *
	 * @param string $filename Absolute path and file name of the ZIP file to open
	 * @return CSimpleUnzip
	 */
	function CSimpleUnzip( $filename )
	{
		// Set the stored filename to the defined parameter
		$this->_filename = $filename;
		
		// Try opening the file
		$this->_filepointer = $this->_getFP();
	}
	
	/**
	 * Returns a file pointer to the ZIP file, or tries to open the file.
	 *
	 * @return integer|boolean File pointer or FALSE when there's an error
	 * @access private
	 */
	function _getFP()
	{
		// If the file is open, return the existing file pointer
		if ( $this->_filepointer !== FALSE ) return $this->_filepointer;
		
		if( file_exists($this->_filename) )
		{
			$fp = @fopen( $this->_filename, 'r');
			if( $fp === false )
			{
				$this->_error = "Could not open " . $this->_filename . " for reading. Check permissions?";
				$this->_isError = true;
				return false; 
			} else {
				$this->_filepointer = $fp;
				return $this->_filepointer;
			}
		} else {
			$this->_error = "File " . $this->_filename . " does not exist.";
			$this->_isError = true;
			return false;
		}
	}
	
	/**
	 * Returns the error message for the current error state, or FALSE if no error has occured
	 *
	 * @return string|boolean Error message or FALSE for no errors
	 */
	function getError()
	{
		if( !$this->_isError ) {
			return false;
		} else {
			return $this->_error;
		}
	}
	
	/**
	 * Extracts a file from the ZIP archive
	 *
	 * @param integer $offset The offset to start extracting from. If ommited, or set to null,
	 * it continues from the ZIP file's current location. 
	 * @return array|boolean A return array or FALSE if an error occured
	 */
	function ExtractFile( $offset = null )
	{
		// Generate a return array
		$retArray = array(
			"file"				=> '',		// File name extracted
			"compressed"		=> 0,		// Compressed size
			"uncompressed"		=> 0,		// Uncompressed size
			"type"				=> "file",	// File type (file | dir)
			"zipoffset"			=> 0,		// Offset in ZIP file
			"done"				=> false	// Are we done with extracting files?
		);
		
		// Get file pointer to the ZIP file
		$fp = $this->_getFP();
		
		// If we can't open the file, return an error condition
		if( $fp === false ) return false;
		
		// Go to the offset specified, if specified. Otherwise, it will continue reading from current position
		if( !is_null($offset) )	fseek( $fp, $offset );
				
		// Get and decode Local File Header
		$headerBinary = fread($fp, 30);
		$headerData = unpack('Vsig/C2ver/vbitflag/vcompmethod/vlastmodtime/vlastmoddate/Vcrc/Vcompsize/Vuncomp/vfnamelen/veflen', $headerBinary);
		// Check signature
		if( $headerData['sig'] == 0x04034b50 )
		{
			// This is a file header. Get basic parameters.
			$retArray['compressed']		= $headerData['compsize'];
			$retArray['uncompressed']	= $headerData['uncomp'];
			$nameFieldLength			= $headerData['fnamelen'];
			$extraFieldLength			= $headerData['eflen'];

			// Read filename field
			$retArray['file']			= fread( $fp, $nameFieldLength );
			// Read extra field if present
			if($extraFieldLength > 0) $extrafield = fread( $fp, $extraFieldLength );
			
			if( strrpos($retArray['file'], '/') == strlen($retArray['file']) - 1 ) $retArray['type'] = 'dir';
			
			// Do we need to create the directory?
			if(strpos($retArray['file'], '/') > 0) {
				$lastSlash = strrpos($retArray['file'], '/');
				$dirName = substr( $retArray['file'], 0, $lastSlash);
				if( $this->_createDirRecursive($dirName) == false ) {
					$this->_isError = true;
					$this->_error = "Could not create $dirName folder";
					return false; 
				}
			}
			
			if( $headerData['compmethod'] == 8 )
			{
				// DEFLATE compression
			
				$zipData = fread( $fp, $retArray['compressed'] );
				$unzipData = gzinflate( $zipData );
				unset($zipData);
				if( DRYRUN != 1 )
				{
					// Try writing to the output file
					$outfp = @fopen( $retArray['file'], 'w' );
					if( $outfp === false ) {
						// An error occured
						$this->_isError = true;
						$this->_error = "Could not open " . $retArray['file'] . " for writing.";
						return false; 
					} else {
						// No error occured. Write to the file.
						fwrite( $outfp, $unzipData, $retArray['uncompressed'] );
						fclose( $outfp );
					}
				}
				unset($unzipData);
			} else {
				if( $retArray['type'] == "file" )
				{
					// No compression
					if( $retArray['uncompressed'] > 0 )
					{
						if( DRYRUN != 1 ) {
							$outfp = @fopen( $retArray['file'], 'w' );
							if( $outfp === false ) {
								// An error occured
								$this->_isError = true;
								$this->_error = "Could not open " . $retArray['file'] . " for writing.";
								return false; 
							} else {
								$readBytes = 0;
								$toReadBytes = 0;
								$leftBytes = $retArray['compressed'];

								while( $leftBytes > 0)
								{
									$toReadBytes = ($leftBytes > CHUNKSIZE) ? CHUNKSIZE : $leftBytes;
									$leftBytes -= $toReadBytes;
									$data = fread( $fp, $toReadBytes );
									fwrite( $outfp, $data );
								}
								fclose($outfp);
							}
						}
					} else {
						// 0 byte file, just touch it
						if( DRYRUN != 1 )
						{
							$outfp = @fopen( $retArray['file'], 'w' );
							if( $outfp === false ) {
								// An error occured
								$this->_isError = true;
								$this->_error = "Could not open " . $retArray['file'] . " for writing.";
								return false; 
							} else {
								fclose($outfp);
							}
						}
					}
				} else {
					if( DRYRUN != 1 ) {
						$result = $this->_createDirRecursive( $retArray['file'] );
						if( !$result ) {
							return false;
						}
					}
				}
			}
			
			$retArray['zipoffset'] = ftell( $fp ); 
			return $retArray;
		} else {
			// This is not a file header. This means we are done.
			$retArray['done'] = true;
			return $retArray;
		}
	}
	
	/**
	 * Tries to recursively create the directory $dirName
	 *
	 * @param string $dirName The directory to create 
	 * @return boolean TRUE on success, FALSE on failure
	 * @access private
	 */
	function _createDirRecursive( $dirName )
	{
		$dirArray = explode('/', $dirName);
		$path = '';
		foreach( $dirArray as $dir )
		{
			$path .= $dir . '/';
			$ret = is_dir($path) ? true : @mkdir($path);
			if( !ret ) {
				$this->_isError = true;
				$this->_error = "Could not create $path folder.";
				return false;
			}
		}
		return true;
	}
}

// ==========================================================================================
// KS: UnJPA: JoomlaPack Archive Extraction Class
// ==========================================================================================
/**
 * JoomlaPack Archive extraction class
 * Implements the JoomlaPack Archive format version 1.0
 */
class CUnJPA
{
	/**
	 * File pointer of the archive opened
	 * @var integer
	 */
	var $_fp = FALSE;
	
	/**
	 * Absolute pathname to archive being operated upon
	 * @var string
	 */
	var $_filename;

	/**
	 * Did we encounter an error?
	 *
	 * @var boolean
	 */
	var $_isError = false;
	
	/**
	 * Error description
	 *
	 * @var string
	 */
	var $_error = '';
	
	/**
	 * Data read from archive's header
	 * @var array
	 */
	var $headerData = array();
	
	/**
	 * The last offset in the archive file we read data from
	 *
	 * @var unknown_type
	 */
	var $lastOffset = 0;
	
	/**
	 * Opens a JPA archive for reading
	 * @param string $filename The absolute pathname to the file being operated upon
	 * @return CUnJPA
	 */
	function CUnJPA( $filename )
	{
		// Store the filename
		$this->_filename = $filename;
		
		// Try opening the file
		$this->_fp = $this->_getFP();
		
		// Read the header
		$this->_ReadHeader();
	}
	
	function Close()
	{
		@fclose( $this->_fp );
	}
	
	/**
	 * Returns the error message for the current error state, or FALSE if no error has occured
	 *
	 * @return string|boolean Error message or FALSE for no errors
	 */
	function getError()
	{
		if( !$this->_isError ) {
			return false;
		} else {
			return $this->_error;
		}
	}

	/**
	 * Extracts a file from the ZIP archive
	 *
	 * @param integer $offset The offset to start extracting from. If ommited, or set to null,
	 * it continues from the ZIP file's current location. 
	 * @return array|boolean A return array or FALSE if an error occured
	 */
	function ExtractFile( $offset = null )
	{
		// Generate a return array
		$retArray = array(
			"file"				=> '',		// File name extracted
			"compressed"		=> 0,		// Compressed size
			"uncompressed"		=> 0,		// Uncompressed size
			"type"				=> "file",	// File type (file | dir)
			"compression"		=> "none",	// Compression type (none | gzip | bzip2)
			"archiveoffset"		=> 0,		// Offset in ZIP file
			"permissions"		=> 0,		// UNIX permissions stored in the archive
			"done"				=> false	// Are we done with extracting files?
		);
		
		$offset = $offset == 0 ? null : $offset;
		
		// Get file pointer to the ZIP file
		$fp = $this->_getFP();
		
		// If we can't open the file, return an error condition
		if( $fp === false ) return false;
		
		// Go to the offset specified, if specified. Otherwise, it will continue reading from current position
		if( is_null($offset) ) $offset = $this->lastOffset;
		if( !is_null($offset) )	fseek( $fp, $offset );
				
		// Get and decode Entity Description Block
		$signature = fread($fp, 3);
		
		// Check signature
		if( $signature == 'JPF' )
		{
			// This a JPA Entity Block. Process the header.
			
			// Read length of EDB and of the Entity Path Data
			$length_array = unpack('vblocksize/vpathsize', fread($fp, 4));
			// Read the path data
			$file = fread( $fp, $length_array['pathsize'] );
			// Read and parse the known data portion
			$bin_data = fread( $fp, 14 );
			$header_data = unpack('Ctype/Ccompression/Vcompsize/Vuncompsize/Vperms', $bin_data);
			// Read any unknwon data
			$restBytes = $length_array['blocksize'] - (21 + $length_array['pathsize']);
			if( $restBytes > 0 ) $junk = fread($fp, $restBytes);
			
			$compressionType = $header_data['compression'];
			
			// Populate the return array
			$retArray['file'] = $file;
			$retArray['compressed'] = $header_data['compsize'];
			$retArray['uncompressed'] = $header_data['uncompsize'];
			$retArray['type'] = ($header_data['type'] == 0 ? "dir" : "file");
			switch( $compressionType )
			{
				case 0:
					$retArray['compression'] = 'none';
					break;
				case 1:
					$retArray['compression'] = 'gzip';
					break;
				case 2:
					$retArray['compression'] = 'bzip2';
					break;
			}
			$retArray['permissions'] = $header_data['perms'];
			
			// Do we need to create the directory?
			if(strpos($retArray['file'], '/') > 0) {
				$lastSlash = strrpos($retArray['file'], '/');
				$dirName = substr( $retArray['file'], 0, $lastSlash);
				if( DRYRUN != 1 )
				{
					if( $this->_createDirRecursive($dirName) == false ) {
						$this->_isError = true;
						$this->_error = "Could not create $dirName folder";
						return false; 
					}
				}
			}
			
			switch( $retArray['type'] )
			{
				case "dir":
					if( DRYRUN != 1 ) {
						$result = $this->_createDirRecursive( $retArray['file'] );
						if( !$result ) {
							return false;
						}
					}
					break;
					
				case "file":
					switch( $compressionType )
					{
						case 0: // No compression
							if( $retArray['uncompressed'] > 0 )
							{
								if( DRYRUN != 1 ) 
								{
									$outfp = @fopen( $retArray['file'], 'w' );
									if( $outfp === false ) {
										// An error occured
										$this->_isError = true;
										$this->_error = "Could not open " . $retArray['file'] . " for writing.";
										return false;
									} 
								}
								$readBytes = 0;
								$toReadBytes = 0;
								$leftBytes = $retArray['compressed'];

								while( $leftBytes > 0)
								{
									$toReadBytes = ($leftBytes > CHUNKSIZE) ? CHUNKSIZE : $leftBytes;
									$leftBytes -= $toReadBytes;
									$data = fread( $fp, $toReadBytes );
									if( DRYRUN != 1 ) fwrite( $outfp, $data );
								}
									if( DRYRUN != 1 ) fclose($outfp);
							} else {
								// 0 byte file, just touch it
								if( DRYRUN != 1 )
								{
									$outfp = @fopen( $retArray['file'], 'w' );
									if( $outfp === false ) {
										// An error occured
										$this->_isError = true;
										$this->_error = "Could not open " . $retArray['file'] . " for writing.";
										return false; 
									} else {
										fclose($outfp);
									}
								}
							}
							break;
							
						case 1: // GZip compression
							$zipData = fread( $fp, $retArray['compressed'] );
							$unzipData = gzinflate( $zipData );
							unset($zipData);
							if( DRYRUN != 1 )
							{
								// Try writing to the output file
								$outfp = @fopen( $retArray['file'], 'w' );
								if( $outfp === false ) {
									// An error occured
									$this->_isError = true;
									$this->_error = "Could not open " . $retArray['file'] . " for writing.";
									return false; 
								} else {
									// No error occured. Write to the file.
									fwrite( $outfp, $unzipData, $retArray['uncompressed'] );
									fclose( $outfp );
								}
							}
							unset($unzipData);
							break;
							
						case 2: // BZip2 compression
							$zipData = fread( $fp, $retArray['compressed'] );
							$unzipData = bzdecompress( $zipData );
							unset($zipData);
							if( DRYRUN != 1 )
							{
								// Try writing to the output file
								$outfp = @fopen( $retArray['file'], 'w' );
								if( $outfp === false ) {
									// An error occured
									$this->_isError = true;
									$this->_error = "Could not open " . $retArray['file'] . " for writing.";
									return false; 
								} else {
									// No error occured. Write to the file.
									fwrite( $outfp, $unzipData, $retArray['uncompressed'] );
									fclose( $outfp );
								}
							}
							unset($unzipData);
							break;
					}
					break;
			}

			$retArray['archiveoffset'] = ftell( $fp );
			$this->lastOffset = $retArray['archiveoffset']; 
			return $retArray;
		} else {
			// This is not a file header. This means we are done.
			$retArray['done'] = true;
			return $retArray;
		}
	}
	
	/**
	 * Returns a file pointer to the ZIP file, or tries to open the file.
	 *
	 * @return integer|boolean File pointer or FALSE when there's an error
	 * @access private
	 */
	function _getFP()
	{
		// If the file is open, return the existing file pointer
		if ( $this->_fp !== FALSE ) return $this->_fp;
		
		if( file_exists($this->_filename) )
		{
			$fp = @fopen( $this->_filename, 'r');
			if( $fp === false )
			{
				$this->_error = "Could not open " . $this->_filename . " for reading. Check permissions?";
				$this->_isError = true;
				return false; 
			} else {
				$this->_fp = $fp;
				return $this->_fp;
			}
		} else {
			$this->_error = "File " . $this->_filename . " does not exist.";
			$this->_isError = true;
			return false;
		}
	}

	/**
	 * Tries to recursively create the directory $dirName
	 *
	 * @param string $dirName The directory to create 
	 * @return boolean TRUE on success, FALSE on failure
	 * @access private
	 */
	function _createDirRecursive( $dirName )
	{
		$dirArray = explode('/', $dirName);
		$path = '';
		foreach( $dirArray as $dir )
		{
			$path .= $dir . '/';
			$ret = is_dir($path) ? true : @mkdir($path);
			if( !ret ) {
				$this->_isError = true;
				$this->_error = "Could not create $path folder.";
				return false;
			}
		}
		return true;
	}	
	
	/**
	 * Reads the files header
	 * @access private
	 * @return boolean TRUE on success
	 */
	function _ReadHeader()
	{
		// Initialize header data array
		$this->headerData = array();
		
		// Get filepointer
		$fp = $this->_getFP();
		
		// Fail for unreadable files
		if( $fp === false ) return false;

		// Go to the beggining of the file
		rewind( $fp );
		
		// Read the signature
		$sig = fread( $fp, 3 );
		
		if ($sig != 'JPA') return false; // Not a JoomlaPack Archive?
		
		// Read and parse header length
		$header_length_array = unpack( 'v', fread( $fp, 2 ) );
		$header_length = $header_length_array[1];
		
		// Read and parse the known portion of header data (14 bytes)
		$bin_data = fread($fp, 14);
		$header_data = unpack('Cmajor/Cminor/Vcount/Vuncsize/Vcsize', $bin_data);
		
		// Load any remaining header data (forward compatibility)
		$rest_length = $header_length - 19;
		if( $rest_length > 0 ) $junk = fread($fp, $rest_length);
		
		$this->headerData = array(
			'signature' => 			$sig,
			'length' => 			$header_length,
			'major' => 				$header_data['major'],
			'minor' => 				$header_data['minor'],
			'filecount' => 			$header_data['count'],
			'uncompressedsize' => 	$header_data['uncsize'],
			'compressedsize' => 	$header_data['csize'],
			'unknowndata' => 		$junk
		);
		
		$this->lastOffset = ftell( $fp );
		
		return true;
	}

}

// ==========================================================================================
// KS: (S)AJAX Library
// ==========================================================================================
if (!isset($SAJAX_INCLUDED)) {

	/*
	 * GLOBALS AND DEFAULTS
	 *
	 */
	$GLOBALS['sajax_version'] = '0.12';
	$GLOBALS['sajax_debug_mode'] = 0;
	$GLOBALS['sajax_export_list'] = array();
	$GLOBALS['sajax_request_type'] = 'POST';
	$GLOBALS['sajax_remote_uri'] = '';
	$GLOBALS['sajax_failure_redirect'] = '';

	/*
	 * CODE
	 *
	 */

	//
	// Initialize the Sajax library.
	//
	function sajax_init() {
	}

	// Since str_split used in sajax_get_my_uri is only available on PHP 5, we have
	// to provide an alternative for those using PHP 4.x
	if(!function_exists('str_split')){
	   function str_split($string,$split_length=1){
	       $count = strlen($string);
	       if($split_length < 1){
	           return false;
	       } elseif($split_length > $count){
	           return array($string);
	       } else {
	           $num = (int)ceil($count/$split_length);
	           $ret = array();
	           for($i=0;$i<$num;$i++){
	               $ret[] = substr($string,$i*$split_length,$split_length);
	           }
	           return $ret;
	       }
	   }
	}

	//
	// Helper function to return the script's own URI.
	//
	function sajax_get_my_uri() {
		return $_SERVER["REQUEST_URI"];
	}

	$sajax_remote_uri = sajax_get_my_uri();

	//
	// Helper function to return an eval()-usable representation
	// of an object in JavaScript.
	//
	function sajax_get_js_repr($value) {
		$type = gettype($value);

		if ($type == "boolean") {
			return ($value) ? "Boolean(true)" : "Boolean(false)";
		}
		elseif ($type == "integer") {
			return "parseInt($value)";
		}
		elseif ($type == "double") {
			return "parseFloat($value)";
		}
		elseif ($type == "array" || $type == "object" ) {
			//
			// XXX Arrays with non-numeric indices are not
			// permitted according to ECMAScript, yet everyone
			// uses them.. We'll use an object.
			//
			$s = "{ ";
			if ($type == "object") {
				$value = get_object_vars($value);
			}
			foreach ($value as $k=>$v) {
				$esc_key = sajax_esc($k);
				if (is_numeric($k))
					$s .= "$k: " . sajax_get_js_repr($v) . ", ";
				else
					$s .= "\"$esc_key\": " . sajax_get_js_repr($v) . ", ";
			}
			if (count($value))
				$s = substr($s, 0, -2);
			return $s . " }";
		}
		else {
			$esc_val = sajax_esc($value);
			$s = "'$esc_val'";
			return $s;
		}
	}

	function sajax_handle_client_request() {
		global $sajax_export_list;

		$mode = "";

		if (! empty($_GET["rs"]))
			$mode = "get";

		if (!empty($_POST["rs"]))
			$mode = "post";

		if (empty($mode))
			return;

		$target = "";

		ob_clean();

		if ($mode == "get") {
			// Bust cache in the head
			header ("Expires: Mon, 26 Jul 1997 05:00:00 GMT");    // Date in the past
			header ("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
			// always modified
			header ("Cache-Control: no-cache, must-revalidate");  // HTTP/1.1
			header ("Pragma: no-cache");                          // HTTP/1.0
			$func_name = $_GET["rs"];
			if (! empty($_GET["rsargs"]))
				$args = $_GET["rsargs"];
			else
				$args = array();
		}
		else {
			$func_name = $_POST["rs"];
			if (! empty($_POST["rsargs"]))
				$args = $_POST["rsargs"];
			else
				$args = array();
		}

		if (! in_array($func_name, $sajax_export_list))
			echo "-:$func_name not callable";
		else {
			echo "+:";
			ob_flush();
			$result = call_user_func_array($func_name, $args);
			echo "var res = " . trim(sajax_get_js_repr($result)) . "; res;";
			ob_flush();
			flush();
		}
		exit;
	}

	function sajax_get_common_js() {
		global $option;
		global $sajax_debug_mode;
		global $sajax_request_type;
		global $sajax_remote_uri;
		global $sajax_failure_redirect;

		$t = strtoupper($sajax_request_type);
		if ($t != "" && $t != "GET" && $t != "POST")
			return "// Invalid type: $t.. \n\n";

		ob_start();
		?>

		// remote scripting library
		// (c) copyright 2005 modernmethod, inc
		var sajax_debug_mode = <?php echo $sajax_debug_mode ? "true" : "false"; ?>;
		var sajax_request_type = "<?php echo $t; ?>";
		var sajax_target_id = "";
		var sajax_failure_redirect = "<?php echo $sajax_failure_redirect; ?>";
		var sajax_failed_eval = "";
		var sajax_fail_handle = "";

		function sajax_debug(text) {
			if (sajax_debug_mode)
				alert(text);
		}

 		function sajax_init_object() {
 			sajax_debug("sajax_init_object() called..")

 			var A;

 			var msxmlhttp = new Array(
				'Msxml2.XMLHTTP.5.0',
				'Msxml2.XMLHTTP.4.0',
				'Msxml2.XMLHTTP.3.0',
				'Msxml2.XMLHTTP',
				'Microsoft.XMLHTTP');
			for (var i = 0; i < msxmlhttp.length; i++) {
				try {
					A = new ActiveXObject(msxmlhttp[i]);
				} catch (e) {
					A = null;
				}
			}

			if(!A && typeof XMLHttpRequest != "undefined")
				A = new XMLHttpRequest();
			if (!A)
				sajax_debug("Could not create connection object.");
			return A;
		}

		var sajax_requests = new Array();

		function sajax_cancel() {
			for (var i = 0; i < sajax_requests.length; i++)
				sajax_requests[i].abort();
		}

		function sajax_do_call(func_name, args) {
			var i, x, n;
			var uri;
			var post_data;
			var target_id;

			sajax_debug("in sajax_do_call().." + sajax_request_type + "/" + sajax_target_id);
			target_id = sajax_target_id;
			if (typeof(sajax_request_type) == "undefined" || sajax_request_type == "")
				sajax_request_type = "GET";

			uri = "<?php echo sajax_get_my_uri() . "?option=$option&no_html=1&act=ajax"; ?>";
			if (sajax_request_type == "GET") {

				if (uri.indexOf("?") == -1)
					uri += "?rs=" + escape(func_name);
				else
					uri += "&rs=" + escape(func_name);
				uri += "&rst=" + escape(sajax_target_id);
				uri += "&rsrnd=" + new Date().getTime();

				for (i = 0; i < args.length-1; i++)
					uri += "&rsargs[]=" + escape(args[i]);

				post_data = null;
			}
			else if (sajax_request_type == "POST") {
				post_data = "option=<?php echo $option; ?>&no_html=1&act=ajax"
				post_data += "&rs=" + escape(func_name);
				post_data += "&rst=" + escape(sajax_target_id);
				post_data += "&rsrnd=" + new Date().getTime();

				for (i = 0; i < args.length-1; i++)
					post_data = post_data + "&rsargs[]=" + escape(args[i]);
			}
			else {
				alert("Illegal request type: " + sajax_request_type);
			}

			x = sajax_init_object();
			if (x == null) {
				if (sajax_failure_redirect != "") {
					location.href = sajax_failure_redirect;
					return false;
				} else {
					sajax_debug("NULL sajax object for user agent:\n" + navigator.userAgent);
					return false;
				}
			} else {
				x.open(sajax_request_type, uri, true);
				// window.open(uri);

				sajax_requests[sajax_requests.length] = x;

				if (sajax_request_type == "POST") {
					x.setRequestHeader("Method", "POST " + uri + " HTTP/1.1");
					x.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
				}

				x.onreadystatechange = function() {
					if (x.readyState != 4)
						return;

					sajax_debug("received " + x.responseText);

					var status;
					var data;
					var txt = x.responseText.replace(/^\s*|\s*$/g,"");
					status = txt.charAt(0);
					data = txt.substring(2);

					if (status == "") {
						// let's just assume this is a pre-response bailout and let it slide for now
					} else if (status == "-")
						alert("Error: " + data);
					else {
						if (target_id != "")
							document.getElementById(target_id).innerHTML = eval(data);
						else {
							try {
								var callback;
								var extra_data = false;
								if (typeof args[args.length-1] == "object") {
									callback = args[args.length-1].callback;
									extra_data = args[args.length-1].extra_data;
								} else {
									callback = args[args.length-1];
								}
								callback(eval(data), extra_data);
							} catch (e) {
								sajax_debug("Caught error " + e + ": Could not eval " + data );
								sajax_failed_eval = data;
								sajax_fail_handle(data);
							}
						}
					}
				}
			}

			sajax_debug(func_name + " uri = " + uri + "/post = " + post_data);
			x.send(post_data);
			sajax_debug(func_name + " waiting..");
			delete x;
			return true;
		}

		<?php
		$html = ob_get_contents();
		ob_end_clean();
		return $html;
	}

	function sajax_show_common_js() {
		echo sajax_get_common_js();
	}

	// javascript escape a value
	function sajax_esc($val)
	{
		$val = str_replace("\\", "\\\\", $val);
		$val = str_replace("\r", "\\r", $val);
		$val = str_replace("\n", "\\n", $val);
		$val = str_replace("'", "\\'", $val);
		return str_replace('"', '\\"', $val);
	}

	function sajax_get_one_stub($func_name) {
		ob_start();
		?>

		// wrapper for <?php echo $func_name; ?>

		function x_<?php echo $func_name; ?>() {
			sajax_do_call("<?php echo $func_name; ?>",
				x_<?php echo $func_name; ?>.arguments);
		}

		<?php
		$html = ob_get_contents();
		ob_end_clean();
		return $html;
	}

	function sajax_show_one_stub($func_name) {
		echo sajax_get_one_stub($func_name);
	}

	function sajax_export() {
		global $sajax_export_list;

		$n = func_num_args();
		for ($i = 0; $i < $n; $i++) {
			$sajax_export_list[] = func_get_arg($i);
		}
	}

	$sajax_js_has_been_shown = 0;
	function sajax_get_javascript()
	{
		global $sajax_js_has_been_shown;
		global $sajax_export_list;

		$html = "";
		if (! $sajax_js_has_been_shown) {
			$html .= sajax_get_common_js();
			$sajax_js_has_been_shown = 1;
		}
		foreach ($sajax_export_list as $func) {
			$html .= sajax_get_one_stub($func);
		}
		return $html;
	}

	function sajax_show_javascript()
	{
		echo sajax_get_javascript();
	}


	$SAJAX_INCLUDED = 1;
}

// ==========================================================================================
// KS: Filesystem abstraction layer
// ==========================================================================================
/**
 * Filesystem Abstraction Module
 *
 * Provides filesystem handling functions in a compatible manner, depending on server's capabilities
 */
class CFSAbstraction {

	/**
	 * Should we use glob() ?
	 * @var boolean
	*/
	var $_globEnable;

	/**
	 * Public constructor for CFSAbstraction class. Does some heuristics to figure out the
	 * server capabilities and setup internal variables
	 */
	function CFSAbstraction()
	{
		// Don't use glob if it's disabled or if opendir is available
		$this->_globEnable = function_exists('glob');
		if( function_exists('opendir') && function_exists('readdir') && function_exists('closedir') )
			$this->_globEnable = false;
	}

	/**
	 * Searches the given directory $dirName for files and folders and returns a multidimensional array.
	 * If the directory is not accessible, returns FALSE
	 * 
	 * @param string $dirName
	 * @param string $shellFilter
	 * @return array See function description for details
	 */
	function getDirContents( $dirName, $shellFilter = null )
	{
		if ($this->_globEnable) {
			return $this->_getDirContents_glob( $dirName, $shellFilter );
		} else {
			return $this->_getDirContents_opendir( $dirName, $shellFilter );
		}
	}

	// ============================================================================
	// PRIVATE SECTION
	// ============================================================================

	/**
	 * Searches the given directory $dirName for files and folders and returns a multidimensional array.
	 * If the directory is not accessible, returns FALSE. This function uses the PHP glob() function.
	 * @return array See function description for details
	 */
	function _getDirContents_glob( $dirName, $shellFilter = null )
	{
		if (is_null($shellFilter)) {
			// Get folder contents
			$allFilesAndDirs1 = @glob($dirName . "/*"); // regular files
			$allFilesAndDirs2 = @glob($dirName . "/.*"); // *nix hidden files

			// Try to merge the arrays
			if ($allFilesAndDirs1 === false) {
				if ($allFilesAndDirs2 === false) {
					$allFilesAndDirs = false;
				} else {
					$allFilesAndDirs = $allFilesAndDirs2;
				}
			} elseif ($allFilesAndDirs2 === false) {
				$allFilesAndDirs = $allFilesAndDirs1;
			} else {
				$allFilesAndDirs = @array_merge($allFilesAndDirs1, $allFilesAndDirs2);
			}

			// Free unused arrays
			unset($allFilesAndDirs1);
			unset($allFilesAndDirs2);

		} else {
			$allFilesAndDirs = @glob($dirName . "/$shellFilter"); // filtered files
		}

		// Check for unreadable directories
		if ( $allFilesAndDirs === FALSE ) {
			return FALSE;
		}

		// Populate return array
		$retArray = array();

		foreach($allFilesAndDirs as $filename) {
			$filename = CFSAbstraction::TranslateWinPath( $filename );
			$newEntry['name'] = $filename;
			$newEntry['type'] = filetype( $filename );
			if ($newEntry['type'] == "file") {
				$newEntry['size'] = filesize( $filename );
			} else {
				$newEntry['size'] = 0;
			}
			$retArray[] = $newEntry;
		}

		return $retArray;
	}

	function TranslateWinPath( $p_path )
    {
		if (stristr(php_uname(), 'windows')){
			// Change potential windows directory separator
			if ((strpos($p_path, '\\') > 0) || (substr($p_path, 0, 1) == '\\')){
				$p_path = strtr($p_path, '\\', '/');
			}
		}
		return $p_path;
	}
	
	function _getDirContents_opendir( $dirName, $shellFilter = null )
	{
		$handle = @opendir( $dirName );

		// If directory is not accessible, just return FALSE
		if ($handle === FALSE) {
			return FALSE;
		}

		// Initialize return array
		$retArray = array();

		while( !( ( $filename = readdir($handle) ) === false) ) {
			$match = is_null( $shellFilter );
			$match = (!$match) ? fnmatch($shellFilter, $filename) : true;
			if ($match) {
				$filename = CFSAbstraction::TranslateWinPath( $dirName . "/" . $filename );
				$newEntry['name'] = $filename;
				$newEntry['type'] = @filetype( $filename );
				if ($newEntry['type'] !== FALSE) {
					// FIX 1.1.0 Stable - When open_basedir restrictions are in effect, an attempt to read <root>/.. could result into failure of the backup. This fix is a simplistic workaround.
					if ($newEntry['type'] == 'file') {
						$newEntry['size'] = @filesize( $filename );
					} else {
						$newEntry['size'] = 0;
					}
					$retArray[] = $newEntry;
				}
			}
		}

		closedir($handle);
		return $retArray;
	}
}

// fnmatch not available on non-POSIX systems
// Thanks to soywiz@php.net for this usefull alternative function [http://gr2.php.net/fnmatch]
if (!function_exists('fnmatch')) {
	function fnmatch($pattern, $string) {
		return @preg_match(
			'/^' . strtr(addcslashes($pattern, '/\\.+^$(){}=!<>|'),
			array('*' => '.*', '?' => '.?')) . '$/i', $string
		);
	}
}

// ==========================================================================================
// KS: Interface
// ==========================================================================================

function showMain()
{
?>
<html>
	<head>
		<title>Hotelier Kickstart</title>
		<style type="text/css">
			body {
				font-family: Arial, Helvetica, sans-serif;
				font-size: 10pt;
				color: #000000;
				background-color: #666666;
			}
			
			h1 {
				font-family: Arial, Helvetica, sans-serif;
				font-size: 18pt;
				display: block;
				height: 22pt;
				padding: 2pt 5pt;
				background-color: #000000;
				color: #ffffff;
				margin: 0px;
			}
			
			#maincontainer {
				display: block;
				background-color: #ffffff;
				border-left: 3pt solid black;
				border-right: 3pt solid black;
			}
			
			#maincontainer p {
				margin: 0px;
			}
			
			#errorbox {
				background-color: #ffffaa;
				border: 3pt solid #ff0000;
				margin: 0pt 5pt;
				padding: 5pt;
				color: #990000;
				display: none;
			}
			
			#process {
				background-color: #eeeeee;
				border: 3px solid #333333;
				margin: 0pt 5pt;
				padding: 5pt;
				color: #000000;
				display: none;
			}
			
			#select {
				padding: 5px;
			}
			
			#footer {
				background-color: #000000;
				color: #aaaaaa;
				font-size: 8pt;
			}
			
			#footer p {
				padding: 2pt;
				margin: 0px;
				text-align: center;
			}
			
			#footer a {
				color: #aaaabb;
			}
			
		</style>
		<script type="text/javascript">
			<?php echo sajax_get_javascript(); ?>
			
			var BytesIn = 0;
			var BytesOut = 0;
			var Files = 0;
			var FileName = '';
			var Offset = '';

			function do_testCompatibility()
			{
				x_testCompatibility( cb_testCompatibility );
			}
			
			function cb_testCompatibility( myRet )
			{
				if( !myRet )
				{
					document.getElementById('errorbox').style.display = 'block';
					document.getElementById('errorbox').innerHTML = '<p>This script is not compatible with your server setup.<br />Make sure PHP Safe Mode is turned off and that the current directory has 0777 permissions.</p>';
				} else {
					do_getZIPList();
				}
			}
			
			function do_getZIPList()
			{
				x_getZIPList( cb_getZIPList );
			}
			
			function cb_getZIPList( myRet )
			{
				document.getElementById('interface').innerHTML = myRet;
			}
			
			function do_startExtract()
			{
				BytesIn = 0;
				BytesOut = 0;
				Files = 0;
				Offset = 0;
				FileName = document.getElementById('zipselect').value;
				do_Extract();
			}
			
			function do_Extract()
			{
				document.getElementById('interface').style.display = 'none';
				x_myExtract( FileName, Offset, cb_Extract );
			}
			
			function cb_Extract( myRet )
			{
				// offset, bytesin, bytesout, files, iserror, error, done
				
				var myHTML = '';
				document.getElementById('process').style.display = 'block';
				
				if( myRet['error'] ) {
					document.getElementById('process').style.display = 'none';
					document.getElementById('errorbox').style.display = 'block';
					document.getElementById('errorbox').innerHTML = myRet['error'];
				} else {
					if( myRet['done'] ) {
						// Done extracting
						document.getElementById('process').style.display = 'none';
						document.getElementById('interface').style.display = 'block';
						document.getElementById('interface').innerHTML = '';
						x_renameHtaccess(true, cb_firstRename);
					} else {
						// Continue extracting
						BytesIn += myRet['bytesin'];
						BytesOut += myRet['bytesout'];
						Files += myRet['files'];
						Offset = myRet['offset'];
						myHTML = "<p>Read: ";
						myHTML += BytesIn;
						myHTML += " bytes<br />Written: ";
						myHTML += BytesOut
						myHTML += " bytes<br />Processed: "
						myHTML += Files;
						myHTML += " files<br /></p>";
						
						document.getElementById('process').innerHTML = myHTML;
						
						do_Extract();
					}
				}
			}
			
			function cb_firstRename( myRet )
			{
				document.getElementById('interface').innerHTML = '<p>Please click' +
					'<a href="installation/index.php" target="_blank"> here</a> to open the Hotelier Installation script ' +
					'in a new window.<br /><b>DO NOT CLOSE THIS WINDOW!!</b>'+
					'<br />When you have finished the installation please click '+
					'<a href="javascript:postInstall();">here</a> to activate your .htaccess '+
					'(if you had one in the first place) and delete the ZIP and this script.</p>';
			}
			
			function postInstall()
			{
				x_renameHtaccess(false, cb_postRename);
			}
			
			function cb_postRename( myRet )
			{
				x_deleteLeftovers( FileName, cb_deleteLeftovers );
			}
			
			function cb_deleteLeftovers( myRet )
			{
				document.getElementById('interface').innerHTML = '<h2>Congratulations!</h2>' +
				'<p>You have successfully completed the hotelier KickStart wizard</p>';
			}
		</script>
	</head>
	<body>
		<h1>Hotelier Kickstart</h1>
		<div id="maincontainer">
			<p>&nbsp;</p>
			<div id="errorbox">
			</div>
			<div id="process">
			</div>
			<div id="interface">
			</div>
			<p>&nbsp;</p>
			<script type="text/javascript">
				do_testCompatibility();
			</script>
		</div>
		<div id="footer">
			<p></p>
		</div>
	</body>
</html>
<?php
}

// ==========================================================================================
// KS: AJAX Functions
// ==========================================================================================

/**
 * Tests compatibility of this script with the server it's running on
 *
 * @return boolean
 */
function testCompatibility()
{
	// 1. Try creating a directory - catches permissions and safe_mode errors
	$myres = @mkdir('jpkickstart');
	if( $myres === false ) return false;
	if( !is_dir('jpkickstart') ) return false;
	@rmdir('jpkickstart');
	
	// 2. Try creating a file
	$fp = @fopen('dummy.txt', 'w');
	if( $fp === false ) return false;
	fclose($fp);
	if( !file_exists('dummy.txt') ) return false;
	@unlink('dummy.txt');
	
	return true;
}

/**
 * Gets the ZIP files present in the current directory and returns a hefty selection box
 *
 * @return unknown
 */
function getZIPList()
{
	$html = "";
	
	$fs = new CFSAbstraction();
	$partialListZIP = $fs->getDirContents( '.', '*.zip' );
	$partialListJPA = $fs->getDirContents( '.', '*.jpa' );
	$allZIPs = array_merge( $partialListJPA, $partialListZIP );
	
	if( count($allZIPs) == 0 ) {
		$html = "<p>There are no ZIP/JPA files in the current directory?!</p>";
	} else {
		$html = '<p id="select">Please select a ZIP/JPA file below and press the &quot;Start&quot; button.<br /><select id="zipselect">';
		foreach( $allZIPs as $thisFile )
		{
			$html .= '<option value="' . $thisFile['name'] . '">' . $thisFile['name'] . '</option>';
		}
		$html .= '</select>&nbsp;';
		$html .= '<input type="button" value="Start" onClick="do_startExtract();" />';
		$html .= '</p>';
	}
	
	return $html;
}

/**
 * Extracts a bacth of files from the ZIP file
 *
 * @param string $filename The ZIP file to extract from
 * @param integer $offset Offset to start unzipping from
 * @return array A return array
 */
function myExtract( $filename, $offset = 0 )
{
	$extension = array_pop(explode('.', $filename));
	switch( $extension )
	{
		case 'zip':
			$zip = new CSimpleUnzip( $filename );
			$isJPA = false;
			break;
			
		case 'jpa':
			$zip = new CUnJPA( $filename );
			$isJPA = true;
			break;
			
		default:
			die("Unexpected file type $extension encountered; this should never happen!");
			break;
	}
	
	$filesRead	= 0;
	$bytesRead	= 0;
	$bytesOut	= 0;

	// Process up to MAXBATCHFILES files in a row, or a maximum of MAXBATCHSIZE bytes
	while( ($filesRead <= MAXBATCHFILES) && ($bytesRead <= MAXBATCHSIZE) )
	{
		$result = $zip->ExtractFile( $offset );
		if( $result === false )
		{
			$retArray = array(
				'offset'	=> $offset,
				'bytesin'	=> 0,
				'bytesout'	=> 0,
				'files' 	=> 0,
				'iserror'	=> true,
				'error'		=> $zip->getError(),
				'done'		=> false
			);
			return $retArray;
		} else {
			if( $result['done'] == false )
			{
				// Increase read counter by ammount of bytes read and increase file read count
				$bytesRead		+= $result['compressed'];
				$bytesOut		+= $result['uncompressed'];
				$filesRead		+= 1;
				// Update next offset
				$offset			= $isJPA ? $result['archiveoffset'] : $result['zipoffset'];
			} else {
				// We are just done extracting!
				$retArray = array(
					'offset'	=> $offset,
					'bytesin'	=> $bytesRead,
					'bytesout'	=> $bytesOut,
					'files' 	=> $filesRead,
					'iserror'	=> false,
					'error'		=> '',
					'done'		=> true
				);
				return $retArray;
			}			
		}
	}
	$retArray = array(
		'offset'	=> $offset,
		'bytesin'	=> $bytesRead,
		'bytesout'	=> $bytesOut,
		'files'		=> $filesRead,
		'iserror'	=> false,
		'error'		=> '',
		'done' => false
	);
	return $retArray;	
}

/**
 * Renames the .htaccess file (if it exists) to htaccess.txt or vice versa
 *
 * @param boolean $isPreInstall If TRUE, renames .htaccess to htaccess.bak. If FALSE performs the inverse.
 * @return unknown
 */
function renameHtaccess( $isPreInstall = true )
{
	if( $isPreInstall ) {
		if( file_exists('htaccess.bak') ) @unlink('htaccess.bak');
		if( file_exists('.htaccess') ) return @rename('.htaccess', 'htaccess.bak');
	} else {
		if( file_exists('htaccess.bak') ) return @rename('htaccess.bak', '.htaccess');
	}
}

/**
 * Deletes the ZIP file and this script (suicide code!)
 *
 * @param string $zipFile The absolute path and name of the ZIP files
 */
function deleteLeftovers( $zipFile )
{
	@unlink( $zipFile );
	@unlink( __FILE__ );
	@_unlinkRecursive('installation');
}

function _unlinkRecursive( $dirName ) {
	$FS = new CFSAbstraction();

	if (is_file( $dirName )) {
		echo "Unlinking file $dirName<br/>";
		die();
		@unlink( $dirName );
	} elseif (is_dir( $dirName )) {
		echo "Recursing directory $dirName<br/>";
		$fileList = $FS->getDirContents( $dirName );
		if ($fileList === false) {
		} else {
			foreach($fileList as $fileDescriptor) {
				switch($fileDescriptor['type']) {
					case "dir":
						if( !((substr($fileDescriptor['name'], -1, 1) == '.') || (substr($fileDescriptor['name'], -1, 2) == '..') ) )
						{
							_unlinkRecursive( $fileDescriptor['name'] );	
						}						
						break;
					case "file":
						@unlink( $fileDescriptor['name'] );
						break;
					// All other types (links, character devices etc) are ignored.
				}
			}
			@rmdir($dirName);
		}
	}
}
// ==========================================================================================
// KS: Main loop
// ==========================================================================================

sajax_export('testCompatibility', 'getZIPList', 'myExtract', 'renameHtaccess', 'deleteLeftovers');
sajax_handle_client_request();

if( empty($_POST["rs"]) ) {
	showMain();
}
?>